/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.parser.fence;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.modelcc.language.LanguageException;
import org.modelcc.language.LanguageSpecification;
import org.modelcc.language.factory.LanguageSpecificationFactory;
import org.modelcc.language.lexis.LexicalSpecification;
import org.modelcc.language.lexis.TokenOption;
import org.modelcc.language.lexis.TokenSpecification;
import org.modelcc.language.metamodel.SimpleLanguageElement;
import org.modelcc.language.metamodel.CompositeLanguageElement;
import org.modelcc.language.metamodel.LanguageModel;
import org.modelcc.language.metamodel.LanguageElement;
import org.modelcc.lexer.Lexer;
import org.modelcc.lexer.LexerException;
import org.modelcc.lexer.LexerFactory;
import org.modelcc.lexer.recognizer.PatternRecognizer;
import org.modelcc.parser.ParserException;
import org.modelcc.parser.ParserFactory;
import org.modelcc.metamodel.Model;

/**
 * ModelCC Fence Parser Generator
 * 
 * @author Luis Quesada (lquesada@modelcc.org)
 */
public class FenceParserFactory extends ParserFactory implements Serializable {

    /**
     * Creates a parser
     * @param m the model
     * @return the parser
     * @throws ParserException
     */
    public FenceParser createParser (Model m) throws ParserException 
    {
        return createParser (m,(Set<PatternRecognizer>)null);
    }

    /**
     * Creates a parser
     * @param m the model
     * @param lexer the lexer
     * @return the parser
     * @throws ParserException
     */
    public FenceParser createParser (Model m, Lexer lexer) throws ParserException 
    {
    	if (!(m instanceof LanguageModel))
    		throw new ParserException("Fence parsers need a LanguageModel as input.");
    	
        try {
            //Type erasure does not allow comparing the generated parser with a specific parser type. Check http://serdom.eu/ser/2007/03/25/java-generics-instantiating-objects-of-type-parameter-without-using-class-literal
            LanguageSpecificationFactory lsf = new LanguageSpecificationFactory();
            LanguageSpecification ls = lsf.create((LanguageModel)m);
            Fence gp = new Fence();

            FenceParser parser = new FenceParser(lexer,gp,ls.getSyntacticSpecification());
            return parser;
        } catch (LanguageException e) {
            throw new ParserException("Unable to create parser",e);
        }
    }

    /**
     * Creates a parser
     * @param m the model
     * @param skip the skip model
     * @return the parser
     * @throws ParserException
     */
    public FenceParser createParser (Model m, Model skip) throws ParserException 
    {
    	if (!(skip instanceof LanguageModel))
    		throw new ParserException("Fence parsers need a LanguageModel skip model as input.");
    	
        Set<PatternRecognizer> ignore = new HashSet<PatternRecognizer>();
        
        if (skip != null)
            fillIgnore(ignore,(LanguageModel)skip,((LanguageModel)skip).getStart());
        
        return createParser(m,ignore);
    }

    /**
     * Creates a parser
     * @param m the model
     * @param ignore the ignore set
     * @return the parser
     * @throws ParserException
     */
    public FenceParser createParser (Model m, Set<PatternRecognizer> ignore) 
    		throws ParserException 
    {
    	if (!(m instanceof LanguageModel))
    		throw new ParserException("Fence parsers need a LanguageModel as input.");
        	
        try {
            //Type erasure does not allow comparing the generated parser with a specific parser type. Check http://serdom.eu/ser/2007/03/25/java-generics-instantiating-objects-of-type-parameter-without-using-class-literal
            LanguageSpecificationFactory lsf = new LanguageSpecificationFactory();
            LanguageSpecification ls = lsf.create((LanguageModel)m);
            Lexer gl = LexerFactory.create(addIgnoredTokens(ls.getLexicalSpecification(),ignore));
            FenceParser parser = new FenceParser(gl,new Fence(),ls.getSyntacticSpecification());
            return parser;
        } catch (LanguageException e) {
            throw new ParserException("Unable to create parser",e);
        } catch (LexerException e) {
            throw new ParserException("Unable to create lexer",e);
        }
    }

    
	private LexicalSpecification addIgnoredTokens (LexicalSpecification ls, Set<PatternRecognizer> ignore)
	{
        if (ignore != null)
            for (Iterator<PatternRecognizer> ite = ignore.iterator();ite.hasNext();)
                ls.addTokenSpecification(new TokenSpecification(null,ite.next(),TokenOption.IGNORE));

        return ls;
	}
    
    private static void fillIgnore(Set<PatternRecognizer> ignore, LanguageModel skip, LanguageElement el) 
    {
    	if (el.getClass().equals(CompositeLanguageElement.class)) {
    		Logger.getLogger(FenceParserFactory.class.getName()).log(Level.SEVERE, "The skip model may not contain composite elements. Element {0} is composite.",new Object[]{el.getElementClass().getCanonicalName()});
    	} else if (el.getClass().equals(SimpleLanguageElement.class)) {
    		ignore.add(((SimpleLanguageElement)el).getPatternRecognizer());
    	} else {
    		for (LanguageElement element: skip.getSubelements().get(el))
    			fillIgnore(ignore,skip,element);
    	}
    }

}
